package util

import (
	"context"
	"fmt"
	"strings"

	"github.com/99designs/gqlgen/graphql"
)

func GetPreloadString(prefix, name string) string { // https://gqlgen.com/reference/field-collection/
	if len(prefix) > 0 {
		return prefix + "." + name
	}

	return name
}

func GetNestedPreloads(
	ctx *graphql.OperationContext,
	fields []graphql.CollectedField,
	prefix string,
) (preloads []string) { // https://gqlgen.com/reference/field-collection/
	for _, column := range fields {
		prefixColumn := GetPreloadString(prefix, column.Name)
		preloads = append(preloads, prefixColumn)
		preloads = append(preloads, GetNestedPreloads(ctx,
			graphql.CollectFields(ctx, column.Selections, nil), prefixColumn)...)
	}

	return
}

// e.g. sometimes input is deeper and we want
// flowBlocks.block.choices => when we fetch block in database we want to strip flowBlocks
func StripPreloads(preloads []string, prefix string) []string {
	if prefix == "" {
		return preloads
	}

	var newPreloads []string
	for _, preload := range preloads {
		if strings.HasPrefix(preload, prefix+".") {
			newPreloads = append(newPreloads, strings.TrimPrefix(preload, prefix+"."))
		}
	}

	return newPreloads
}

func GetPreloadsFromContext(ctx context.Context, level string) []string { // https://gqlgen.com/reference/field-collection/
	return StripPreloads(GetNestedPreloads(
		graphql.GetOperationContext(ctx),
		graphql.CollectFieldsCtx(ctx, nil),
		"",
	), level)
}

func PreloadsContainMoreThanID(a []string, v string) bool {
	for _, av := range a {
		if strings.HasPrefix(av, v) &&
			av != v && // e.g. parentTable
			!strings.HasPrefix(av, v+".id") { // e.g parentTable.id
			return true
		}
	}

	return false
}

func PreloadsContain(a []string, v string) bool {
	for _, av := range a {
		if av == v {
			return true
		}
	}

	return false
}

func getRpcPreloads(
	jsonPreloads []string,
	rpcEntityName string,
	nested int, /// 嵌套层级
	rpcEntityPrefix string,
) (map[string][]string, map[string][]string) {
	// get column settings for current model
	entitySettings, hasEntitySettings := RpcEntityPreloadMap[rpcEntityName]
	if !hasEntitySettings {
		return nil, nil
	}

	preloadMap := make(map[string][]string)
	eagerLoadMap := make(map[string][]string)

	for _, jsonPreload := range jsonPreloads {
		// skip .id, .name, .whatever only pick root table for now
		if strings.Count(jsonPreload, ".") > 0 {
			continue
		}

		// get column setting for current preload
		columnSetting, hasColumnSetting := entitySettings[jsonPreload]
		if hasColumnSetting {
			rpcEntityKey := rpcEntityName
			if rpcEntityPrefix != "" {
				rpcEntityKey = rpcEntityPrefix + "." + rpcEntityName
			}

			// get nested preloads for this relation
			if columnSetting.RpcEntityName != "" {
				// if root table has a foreign key available (inside the table) we don't need to preload the whole table
				// if the user only wanted the id of that table
				if columnSetting.IDAvailable {
					if PreloadsContainMoreThanID(jsonPreloads, jsonPreload) {
						eagerLoadMap[rpcEntityName] = append(eagerLoadMap[rpcEntityName], columnSetting.RpcEntityName)
					}
				} else {
					eagerLoadMap[rpcEntityName] = append(eagerLoadMap[rpcEntityName], columnSetting.RpcEntityName)
				}

				level := jsonPreload
				if columnSetting.Level != "" {
					level = jsonPreload + "." + columnSetting.Level
					if columnSetting.Level == ColumnSettingLevelSelf { // 同级字段
						level = ""
					}
				}
				innerPreloadMap, innerEagerLoadMap := getRpcPreloads(
					StripPreloads(jsonPreloads, level),
					columnSetting.RpcEntityName,
					nested+1,
					rpcEntityKey,
				)
				if innerPreloadMap == nil {
					continue
				}

				for key, arr := range innerPreloadMap {
					if len(arr) > 0 {
						preloadMap[key] = append(preloadMap[key], arr...)
					}
				}
				for key, arr := range innerEagerLoadMap {
					if len(arr) > 0 {
						eagerLoadMap[key] = append(eagerLoadMap[key], arr...)
					}
				}
			} else if columnSetting.Name != "" {
				var columns []string
				if columnSetting.Extra != "" {
					columns = strings.Split(columnSetting.Extra, ColumnSettingMultipleSeparator)
				}
				columns = append(columns, columnSetting.Name)
				preloadMap[rpcEntityName] = append(preloadMap[rpcEntityName], columns...)
			}
		}
	}

	return preloadMap, eagerLoadMap
}


func GetPreloads(ctx context.Context, rpcEntityName string) (string, string) {
	return GetPreloadsWithLevel(ctx, rpcEntityName, "")
}

func GetPreloadsWithLevel(
	ctx context.Context,
	rpcEntityName string,
	level string,
) (string, string) {
	jsonPreloads := GetPreloadsFromContext(ctx, level) /// 通过StripPreloads保证层级正确

	// e.g. jsonPreloads: [user.organization.id, user.friends.organization]
	preloadMap, eagerLoadMap := getRpcPreloads(jsonPreloads, rpcEntityName, 0, "")
	var preloads []string
	var eagerLoads []string

	for key, arr := range preloadMap {
		if len(arr) > 0 {
			preloads = append(preloads, fmt.Sprintf("%v:%v", key, strings.Join(arr, ",")))
		}
	}
	for key, arr := range eagerLoadMap {
		if len(arr) > 0 {
			eagerLoads = append(eagerLoads, fmt.Sprintf("%v:%v", key, strings.Join(arr, ",")))
		}
	}

	return strings.Join(preloads, ";"), strings.Join(eagerLoads, ";")
}
